package query.delete

import ast.expr.SqlIdentifierExpr
import ast.statement.delete.SqlDelete
import dsl.column
import expr.DB
import expr.Query
import expr.QueryTableColumn
import expr.TableSchema
import query.ReviseQuery
import util.toSqlString
import visitor.getQueryExpr
import java.sql.Connection
import java.sql.SQLException
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.declaredMemberProperties

class Delete(
    var db: DB = DB.MYSQL,
    override var conn: Connection? = null,
    override var isTransaction: Boolean = false
) : ReviseQuery() {
    constructor(db: DB) : this(db, null, false)

    private var sqlDelete = SqlDelete()

    infix fun from(table: String): Delete {
        sqlDelete.table = SqlIdentifierExpr(table)
        return this
    }

    infix fun <T : TableSchema> from(table: T): Delete {
        return from(table.tableName)
    }

    infix fun delete(entity: Any): Delete {
        val clazz = entity::class
        val fields = clazz.declaredMemberProperties
            .map { it.name to it.getter.call(entity) }
            .toMap()

        val companion = clazz.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val companionClass = companion::class
        val table = companion as TableSchema
        from(table.tableName)

        val pkCols = companionClass.declaredMemberProperties
            .asSequence()
            .map { it.name to it.getter.call(companion) }
            .filter { it.second is QueryTableColumn }
            .map { it.first to (it.second as QueryTableColumn) }
            .filter { it.second.primaryKey }
            .map { it.second.column to fields[it.first] }
            .toMap()

        if (pkCols.isEmpty()) {
            throw SQLException("实体类的伴生对象中没有设置主键字段")
        }

        pkCols.forEach {
            if (it.value == null) {
                throw SQLException("主键为空")
            } else {
                where(column(it.key) eq it.value)
            }
        }
        return this
    }

    @JvmName("deleteById")
    inline infix fun <reified T> delete(primaryKey: Any): Delete {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")

        val tableName = (companion as TableSchema).tableName

        val companionClass = companion::class
        val pkCols = companionClass.declaredMemberProperties
            .map { it.getter.call(companion) }
            .filterIsInstance<QueryTableColumn>()
            .filter { it.primaryKey }

        if (pkCols.isEmpty()) {
            throw SQLException("实体类的伴生对象中没有设置主键字段")
        }

        if (pkCols.size > 1) {
            throw SQLException("主键字段数量和传入的参数不匹配")
        }

        val pkCol = pkCols[0]

        return from(tableName).where(pkCol eq primaryKey)
    }

    inline fun <reified T> delete(primaryKeys: List<Pair<String, Any>>): Delete {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")

        val tableName = (companion as TableSchema).tableName

        val companionClass = companion::class
        val pkCols = companionClass.declaredMemberProperties
            .map { it.name to it.getter.call(companion) }
            .filter { it.second is QueryTableColumn }
            .map { it.first to it.second as QueryTableColumn }
            .filter { it.second.primaryKey }
            .toMap()

        if (pkCols.isEmpty()) {
            throw SQLException("实体类的伴生对象中没有设置主键字段")
        }

        if (pkCols.size != primaryKeys.size) {
            throw SQLException("主键字段数量和传入的参数不匹配")
        }

        from(tableName)
        primaryKeys.forEach {
            if (pkCols.containsKey(it.first)) {
                where(pkCols[it.first]!! eq it.second)
            }
        }

        return this
    }

    inline fun <reified T> delete(primaryKeys: Map<String, Any>): Delete {
        return delete<T>(primaryKeys.map { it.key to it.value })
    }

    infix fun where(condition: Query): Delete {
        this.sqlDelete.addCondition(getQueryExpr(condition, this.db).expr)
        return this
    }

    fun where(test: () -> Boolean, condition: Query): Delete {
        if (test()) {
            where(condition)
        }

        return this
    }

    fun where(test: Boolean, condition: Query): Delete {
        if (test) {
            where(condition)
        }

        return this
    }

    override fun sql() = toSqlString(sqlDelete, db)
}